/**
 * SerialDuplexXbee
 * Send packets to and receive packets from an XBee Series 2 Zigbee radio.
 *
 * Reference:
 * XBee ZB:
 *   http://ftp1.digi.com/support/documentation/90000976_C.pdf
 * XBee Smart Energy:
 *   http://www.digi.com/standards/smart-energy/assets/90033931_A.pdf
 * Processing:
 *   http://processing.org/reference/
 *
 * The intent of this sketch is essentially to document the XBee API
 * with working code.
 *
 * Derived from:
 *   http://processing.org/learning/libraries/serialduplex.html
 *
 * Serial Duplex 
 * by Tom Igoe. 
 * 
 * Sends a byte out the serial port when you type a key
 * listens for bytes received, and displays their value. 
 * This is just a quick application for testing serial data
 * in both directions. 
 */


import processing.serial.*;

Serial myPort;      // The serial port
int whichKey = -1;  // Variable to hold keystoke values
int inByte = -1;    // Incoming serial data
int state = 0;
int ATcursor = 0;
int frameID = 1;
int secondCount = 0;
int delayCount = 0;
int secondsBetweenRequests = 5;
boolean displayIncomingBytes = false;
boolean displayFrame = true;
boolean running = false;
int incomingDataFrameLength;
char incomingDataFrame[];
int incomingCheckSum;
int outgoingDataFrameLength;
String portName;
HashMap moduleTypeMap = new HashMap();
HashMap deviceProfileMap = new HashMap();
HashMap ATdescriptions = new HashMap();
HashMap addr64to16 = new HashMap();
HashMap addr16to64 = new HashMap();
String SerialH = "Unknown";
String SerialL = "Unknown";
String networkAddress = "Unknown";
String operatingChannel = "Unknown";
char moduleType = 'U';  // Unknown
String moduleTypeStr = "Unknown";
char deviceProfile = 'U';
String deviceProfileStr = "Unknown";
String moduleTypes[] = {
  "31", "Coordinator",
  "33", "Router",
  "39", "End Device",
  "21", "Coordinator",
  "23", "Router",
  "29", "End Device"  
};
String deviceProfiles[] = {
  "31", "Smart Energy",
  "33", "Smart Energy",
  "39", "Smart Energy",
  "21", "ZB Drop-in Networking",
  "23", "ZB Drop-in Networking",
  "29", "ZB Drop-in Networking"  
};
String ATcommands[] = {
  // This list if from X-CTU.exe reading an XB24-B
  // with XBee ZB version 2141 firmware
  // Networking
  "ID", "PAN ID",
  "SC", "Scan Channels",
  "SD", "Scan Duration",
  "ZS", "Zigbee Stack Profile",
  "NJ", "Node Join Time",
  "OP", "Operating PAN ID",
  "OI", "Operating 16-bit PAN ID",
  "CH", "Operating Channel",
  "NC", "Number of Remaining Children",
  // Addressing
  "DH", "Destination Address High",
  "DL", "Destination Address Low",
  "NI", "Node Identifier",  
  "NH", "Maximum Hops",
  "BH", "Broadcast Radius",
  "AR", "Many-to-One Route Broadcast Time",
  "DD", "Device Type Identifier",
  "NT", "Node Discovery Backoff",
  "NO", "Node Discovery Options",
  "SH", "Serial Number High",
  "SL", "Serial Number Low",
  "MY", "16-bit Network Address",
  "NP", "Maximum Number of RF Payload Bytes",
  // RF Interfacing
  "PL", "Power Level",
  "PM", "Power Mode",
  // Security  
  "EE", "Encryption Enable",
  "EO", "Encryption Options",
  "KY", "Encryption Key",
  // Serial Interfacing
  "BD", "Baud Rate",
  "NB", "Parity",
  "D7", "DI07 Configuration",
  "D6", "DI06 Configuration",
  "AP", "API Enable",
  "AO", "API Output Mode",
  // Sleep Modes
  "SP", "Cyclic Sleep Period",
  "SN", "Number of Cyclic Sleep Periods",
  // I/O Settings
  "DO", "AD0/DIO0 Configuration",
    // I'm skipping some of these
  "PR", "Pull-up Resistor Enable",
  "LT", "Associate LED Blink Time",
  "RP", "RSSI PWM Timer",
  // Diagnostic commands
  "VR", "Firmware Version",
  "HV", "Hardware Version",
  "AI", "Association Indication",
  "DB", "RSSI of Last Packet",
  // End of ZB Coordinator AT commands
  
  // Commands from other devices
  // End device sleep modes
  "SM", "Sleep Mode",
  "ST", "Time before Sleep",
  "SO", "Sleep Options",
  
  //  * http://www.digi.com/standards/smart-energy/assets/90033931_A.pdf#page=107
  "NP", "16-bit Parent Netword Address",

  // End of list
  "XX", "End of List"
};

/*
 * Processing requires setup()
 */
void setup() {
  size(400, 300);
  // create a font with the third font available to the system:
  PFont myFont = createFont(PFont.list()[2], 14);
  textFont(myFont);

  // List all the available serial ports:
  println(Serial.list());
  // println(binary(unbinary("1100") ^ unbinary("1010")));  // ^ does bitwise XOR

  // At present I'm running two copies of this sketch on the the same
  // machine, one for the coordinator and one for the end device.
  // The file portindex.cfg which is in the /data directory contains
  // a single digit which is the index of which COM: port to use.
  // Todo: make a single instance handle both send & receive
  String[] portFile = loadStrings("portindex.cfg");
  int portIndex = int(portFile[0]);
  portName = Serial.list()[portIndex];
  println("Opening " + portName + " (index: " + portIndex + ")");
  myPort = new Serial(this, portName, 9600);
  populateHashMap(ATdescriptions, ATcommands);
  populateHashMap(moduleTypeMap, moduleTypes);
  populateHashMap(deviceProfileMap, deviceProfiles);
  // test();
  getDisplayData();
}

/*
 * and Processing requires draw()
 */
void draw() {
  int ATidx = ATcursor * 2;
  int position = 70;
  int spacing = 20;
  background(0);
  text("Port: " + portName + " " + deviceProfileStr + " " + moduleTypeStr, 10, position);
  position += spacing;
  text("Last Sent: " + whichKey, 10, position);
  position += spacing;
  text("Last Received: " + inByte, 10, position);
  position += spacing;
  text("AT: " + ATcommands[ATidx] + " " + ATcommands[ATidx+1], 10, position);
  position += spacing;
  text("Serial: " + SerialH + " " + SerialL, 10, position);
  position += spacing;
  text("Network address: " + networkAddress, 10, position);
  position += spacing;
  text("Operating Channel: " + operatingChannel, 10, position);
  position += spacing;
  text("Running: " + running, 10, position);
  position += spacing;
  delay(20); // 20 milliseconds
  delayCount += 20;
  if (delayCount > 1000) {
    delayCount = 0;
    secondCount++;
    if (running && moduleType == 'C' && secondCount % secondsBetweenRequests == 0) {
      sendToUART(makeExplicitBroadcastFrame("Readings.request" , false));
    }
  }
}

/**
 * test() - Put test code snippets here - called at the end of setup()
 */
void test() {
// Examples from
// http://ftp1.digi.com/support/documentation/90000976_C.pdf#page=85
  char atNJ[] = { 0x7E, 0x00, 0x05, 0x08, 0x01, 0x4E, 0x4A, 0xFF, 0x5F };
  String atNJs = new String(atNJ); // AT NJ command (allow joining)
  char atNJframe[] = { 
    0x08, 0x01, 0x4E, 0x4A, 0xFF };
  String atNJframes = makeUARTdataFrame(atNJframe,false);
  
  char atnd[] = { 0x7E, 0x00, 0x04, 0x08, 0x01, 0x4E, 0x44, 0x64 };
  String atnds = new String(atnd); // AT ND command (discover devices in the PAN)
  char atndframe[] = { 
    0x08, 0x01, 0x4E, 0x44 };
  String atndframes = makeUARTdataFrame(atndframe,false);
  
  char escdemo[] = { 0x7E, 0x00, 0x02, 0x23, 0x7D, 0x31, 0xCB };  // This is the *escaped* string
  String escdemos = new String(escdemo); // test escape mechanism
  char escdemoframe[] = { 0x23, 0x11 };  // this is the *unescaped* data
  String escdemoframes =  makeUARTdataFrame(escdemoframe,true);
    if (!atNJs.equals(atNJframes)) {
    println("makeUARTdataFrame error");
  }
  if (!atnds.equals(atndframes)) {
    println("makeUARTdataFrame error");
  }
  if (!escdemos.equals(escdemoframes)) {
    println("makeUARTdataFrame error");
  }

  // println(atframes);
  // println(atdemos);
  printStringAsHex(atNJs);
  printStringAsHex(atNJframes);
  String atNJframess = makeATcommmandFrame("NJ", str(char(0xFF)), false);
  printStringAsHex(atNJframess);
  // printStringAsHex(atdemos);
  // printStringAsHex(escaped0x11);
  printStringAsHex(escdemos);
  printStringAsHex(escdemoframes);
  
  HashMap hm1 = new HashMap();
    hm1.put("apple",1);
  hm1.put("bear",2);
  println(hm1.get("apple"));
  
  HashMap hm2 = new HashMap();
  hm2.put("apple", "fruit");
  hm2.put("bear", "animal");
  hm2.put("bear", "mammal");
  println(hm2.get("bear"));
  println(int("0x123ABC"));
  int foo = unhex("FFEEDDCC");
  println(foo);
  println(hex(foo));
}

void serialEvent(Serial myPort) {
  inByte = myPort.read();
  if (state == 0 && inByte == 0x7E) {
    println();
    println();
    printTime();
    print(" <<<uart: ");
  }
  if (displayIncomingBytes) {
    print(hex(inByte,2) + " ");
  }
  switch (state) {
  case 0:    // Waiting for 7E
    if (inByte == 0x7E) {
      state = 1;
    }
    break;
  case 1:    // Waiting for Length MSB
    incomingDataFrameLength = inByte<<8;
    state = 2;
    break;
  case 2:    // Waiting for Length LSB
    incomingDataFrameLength += inByte;
    // Todo: make this 
    // incomingDataFrame = new char[incomingDataFrameLength+3];
    // and adjust offsets accordingly
    incomingDataFrame = new char[incomingDataFrameLength+4]; // Leave room for 
    incomingDataFrame[0] = 0x7E;
    incomingDataFrame[1] = char(incomingDataFrameLength>>8);
    incomingDataFrame[2] = char(inByte);
    incomingCheckSum = 0;
    state = 3;
    break;
  case 3:    // Waiting for Data Frame byte
    incomingDataFrame[incomingDataFrame.length - incomingDataFrameLength - 1] = char(inByte);
    incomingCheckSum += inByte;
    incomingDataFrameLength--;
    if (incomingDataFrameLength <= 0) {
      state = 4;
    }
    break;
  case 4:    // Waiting for checksum
    incomingDataFrame[incomingDataFrame.length - incomingDataFrameLength - 1] = char(inByte);
    incomingCheckSum = (incomingCheckSum + inByte) & 0xFF;
    if (incomingCheckSum == 0xFF) {
      processIncomingDataFrame(incomingDataFrame);
    }
    state = 0;
    break;
  default:
    println("Unexpected byte: " + hex(inByte,2));
    break;
  }
}

/*
 * frame[] contains the whole frame from 0x7E to checksum byte.
 */
void processIncomingDataFrame(char frame[]) {
  if (displayFrame) {
    printHexFromPacket("raw", frame, 0, frame.length);
    String frameStr = new String(frame);
    println();
    // println(frameStr);  // some of the characters printed make it so that copy & paste doesn't work between the terminal screen & gedit
    println();
  }
  switch (frame[3]) {
    case 0x88:
      String responseCommand = "" + frame[5] + frame[6];
      print("AT " + responseCommand);
      printHexFromPacket(" Status", frame, 7, 1);
      printHexFromPacket("Data", frame, 8, frame.length - 9);
      print (ATdescriptions.get(responseCommand));
      if (responseCommand.equals("VR")) {
        String versionMSB = hex(int(frame[8]),2);
        moduleTypeStr = (String) moduleTypeMap.get(versionMSB);
        moduleType = moduleTypeStr.charAt(0);
        deviceProfileStr = (String) deviceProfileMap.get(versionMSB);
        deviceProfile = deviceProfileStr.charAt(0);
        println(deviceProfileStr + " " + moduleTypeStr);
      } else if (responseCommand.equals("SH")) {
        SerialH = hexStringFromPacket("", frame, 8, 4, "");
      } else if (responseCommand.equals("SL")) {
        SerialL = hexStringFromPacket("", frame, 8, 4, "");
      } else if (responseCommand.equals("MY")) {
        networkAddress = hexStringFromPacket("", frame, 8, 2, "");
      } else if (responseCommand.equals("CH")) {
        operatingChannel = hexStringFromPacket("", frame, 8, 1, "");
      }
      // println();
      break;
    case 0x8A:
      println("Modem status 8A: " + hex(frame[4],2));
      switch (moduleType) {
        case 'E':
        case 'R':
          sendToUART(makeExplicitToCoordinatorFrame("Waking up", false));
          break;
        case 'C':
          break;
      }
      getDisplayData();
      break;
    case 0x8B:
      println("Transmit Status 8B: ");
      printHexFromPacket("Frame ID", frame, 4, 1);
      printHexFromPacket("16-bit address of destination", frame, 5, 2);    
      printHexFromPacket("Retry count", frame, 7, 1);    
      printHexFromPacket("Delivery Status", frame, 8, 1);    
      printHexFromPacket("Discovery Status", frame, 9, 1);    
      break;
    case 0x90:  // The Digi Smart Energy Profile firmware doesn't use 0x90. Digi ZB firmware does support 0x90.
      println("Receive Packet 90: ");
      // This is an actual received packet:
      // 7E 00 11 90 00 13 A2 00 40 31 81 4B 18 F3 02 54 65 61 34 32 F0
      // There is an error in the sample 0x90 packet here:
      // http://ftp1.digi.com/support/documentation/90000976_C.pdf#page=96
      // The sample packet has on 7 bytes for the 64-bit Source address
      printHexFromPacket("Frame ID", frame, 4, 1);
      printHexFromPacket("64-bit Source Address", frame, 4, 8);
      printHexFromPacket("16-bit Source Network Address", frame, 11, 2);
      print(" len: " + str(frame.length - 16) + " ");
      printHexFromPacket("Message", frame, 15, frame.length - 16);
      printTextFromPacket("Message", frame, 15, frame.length - 16);
      break;
    case 0x91:
      println("Explicit Rx Indicator 91: ");
      printHexFromPacket("64-bit Source Address: ", frame, 4, 8);
      String addr64 = hexStringFromPacket("", frame, 4, 8, "");
      printHexFromPacket("16-bit Source Network Address", frame, 12, 2);
      String addr16 = hexStringFromPacket("", frame, 12, 2, "");
      addr64to16.put(addr64, addr16);
      addr16to64.put(addr16, addr64);
      printHexFromPacket("Source Endpoint", frame, 14, 1);
      printHexFromPacket("Destination Endpoint", frame, 15, 1);
      printHexFromPacket("Cluster ID", frame, 16, 2);
      printHexFromPacket("Profile ID", frame, 18, 2);
      printHexFromPacket("Receive Options", frame, 20, 1);
      printHexFromPacket("Received RF data", frame, 21, frame.length - 22);
      printTextFromPacket("Received RF data", frame, 21, frame.length - 22);
      String incomingPayload = textStringFromPacket(frame, 21, frame.length - 22);
      switch (moduleType) {
        case 'C':
          processCoordinatorPayload(incomingPayload);
          break;
        case 'E':
          // sendToUART(makeExplicitToCoordinatorFrame("55 Watts", false));
          break;
        case 'R':
          processRouterPayload(incomingPayload);
          break;
      }
      break;
    default:
      println("Unknown packet");
      printHexFromPacket("", frame, 0, frame.length);
      break;
  }
}

void processCoordinatorPayload(String payload) {
  println();
  println("Payload: " + payload);
}

void processRouterPayload(String payload) {
  if (payload.equals("Readings.request")) {
    sendToUART(makeExplicitToCoordinatorFrame("Readings.indication?volts=" + str(int(random(130))) + "&amps=" + str(int(random(130))), false)); 
  }
}

String XBeeAPIescape(char c) {
  char escChars[] = { 0x7E, 0x7D, 0x11, 0x13 };
  String escCharsStr = new String(escChars);
  String result;
  if (escCharsStr.indexOf(str(c)) != -1) {
    result = str(char(0x7D)) + str(char(c ^ 0x20));  // ^ is XOR
  } 
  else {
    result = str(c);
  }
  return result;
}

String makeBroadcastFrame(String msg, boolean escaped) {
  // see http://ftp1.digi.com/support/documentation/90000976_C.pdf#page=89
  char[] broadcastFrame = { 
    0x10,    // Transmit Request
    0x01,    // Frame ID
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,  // 64-bit Destination Address
    0xFF, 0xFE,    // 16-bit Destination Network Address
    0x00,    // Broadcast Radius
    0x00    // Options
  };
  broadcastFrame[1] = nextFrameID();
  String bf = new String(broadcastFrame);
  return makeUARTdataFrame(string2array(bf + msg),false);
}

String makeExplicitBroadcastFrame(String msg, boolean escaped) {
  // See http://ftp1.digi.com/support/documentation/90000976_C.pdf#page=91
  char[] broadcastFrame = { 
    0x11,    // Transmit Request
    0x01,    // Frame ID
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,  // 64-bit Destination Address
    0xFF, 0xFE,    // 16-bit Destination Network Address
    0xE8,    // Source endpoint for the transmission
    0xE8,    // Destination enpoint for the transmission
    0x00, 0x11,  // Cluster ID used for the transmission
    0xC1, 0x05,  // Profile ID used for the transmission
    0x00,    // Broadcast Radius
    0x00    // Transmit Options
  };
  broadcastFrame[1] = nextFrameID();
  String bf = new String(broadcastFrame);
  return makeUARTdataFrame(string2array(bf + msg),false);
}

String makeExplicitAddrFrame(String addr64, String addr16, String msg, boolean escaped) {
  // See http://ftp1.digi.com/support/documentation/90000976_C.pdf#page=91
  char[] frame = { 
    0x11,    // Transmit Request
    0x01,    // Frame ID
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,  // 64-bit Destination Address
    0xFF, 0xFE,    // 16-bit Destination Network Address
    0xE8,    // Source endpoint for the transmission
    0xE8,    // Destination enpoint for the transmission
    0x00, 0x11,  // Cluster ID used for the transmission
    0xC1, 0x05,  // Profile ID used for the transmission
    0x00,    // Broadcast Radius
    0x00    // Transmit Options
  };
  frame[1] = nextFrameID();
  // String addr64 = hashMapRandomKey(addr64to16);
  // String addr16 = addr64to16.get(addr64);
  // void insertBigEndian(char[] frame, int value, int start, int n) {

  String addr64H = addr64.substring(0,8);
  String addr64L = addr64.substring(8);
  // println(addr64H);
  // println(addr64L);
  // println(unhex(addr64H));
  // println(unhex(addr64L));
  insertBigEndian(frame, unhex(addr64.substring(0,8)), 2, 4);
  insertBigEndian(frame, unhex(addr64.substring(8)), 6, 4);
  insertBigEndian(frame, unhex(addr16), 10, 2);  // Does this make a difference?
  String bf = new String(frame);
  return makeUARTdataFrame(string2array(bf + msg),false);
}

/*
from:
http://ftp1.digi.com/support/documentation/90000976_C.pdf#page=41
also
http://www.digi.com/standards/smart-energy/assets/90033931_A.pdf#page=46

If the explicit transmit frame is used, the cluster ID should be set to 0x0011, the profile ID to
0xC105, and the source and destination endpoints to 0xE8 (recommended defaults for data
transmissions in the Digi profile.) The same transmission could be sent using the following explicit
transmit frame:
7E 00 15 11 01 0000 0000 0000 0000 FFFE E8 E8 0011 C105 00 00 31 18
 */
String makeExplicitToCoordinatorFrame(String msg, boolean escaped) {
  // See http://ftp1.digi.com/support/documentation/90000976_C.pdf#page=91
  char[] frame = { 
    0x11,    // Transmit Request
    0x01,    // Frame ID
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 64-bit Destination Address
                        // All zeros goes to the coordinator
    0xFF, 0xFE,    // 16-bit Destination Network Address
    0xE8,    // Source endpoint for the transmission
    0xE8,    // Destination enpoint for the transmission
    0x00, 0x11,  // Cluster ID used for the transmission
    0xC1, 0x05,  // Profile ID used for the transmission
    0x00,    // Broadcast Radius
    0x00    // Transmit Options
  };
  frame[1] = nextFrameID();
  String cf = new String(frame);
  return makeUARTdataFrame(string2array(cf + msg),false);
}

String makeZDOactiveEndpointsRequestFrame(int networkAddress16) {
  // See http://ftp1.digi.com/support/documentation/90000976_C.pdf#page=101
  // "shows how the Explicit API frame can be used to send an "Active Endpoints"
  // request to discover the active endpoints on a device with a 16-bit address of 0x1234."
  // (I think the example has the cluster ID and the profile ID mixed up)
  char[] frame = { 
    0x11,       // 00 | Transmit Request
    0x01,       // 01 | Frame ID
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,  // 2 | 64-bit Destination Address
    0xFF, 0xFE, // 10 | 16-bit Destination Network Address
    0x00,       // 12 | Source endpoint - set to 0x00 for ZDO transmissions
    0x00,       // 13 | Destination enpoint - set to 0x00 for ZDO transmissions
    0x00, 0x05, // 14 | Cluster ID - set to the cluster ID of the ZDO command being sent
    0x00, 0x00, // 16 | Profile ID - set to 0x0000 for ZDO transmissions
                // (Profile ID 0x0000 is the Zigbee Device Profile that supports ZDOs) 
    0x00,       // 18 | Broadcast Radius
    0x00,       // 19 | Transmit Options
    0x01,       // 20 | Data payload: Transaction Sequence Number - See Sending a ZDO command
                // See http://ftp1.digi.com/support/documentation/90000976_C.pdf#page=50
    0x34, 0x12  // 21 |ZDO payload: 16-bit Network Address sent little endian (0x1234)
  };
  frame[1] = nextFrameID();
  frame[20] = nextFrameID();
  frame[21] = char(networkAddress16 & 0xFF);
  frame[22] = char(networkAddress16 >> 8);
  // frame[21] = char(int("0x" + networkAddress16.substring(2,4)));
  // frame[22] = char(int("0x" + networkAddress16.substring(0,2)));
  String bf = new String(frame);
  return makeUARTdataFrame(string2array(bf),false);
}

String makeReadHardwareVersionAttributeFrame(int addrH, int addrL) {
  // See http://ftp1.digi.com/support/documentation/90000976_C.pdf#page=108
  char[] frame = { 
    0x11,       // 00 | Transmit Request
    0x01,       // 01 | Frame ID
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 2 | 64-bit Destination Address
    0xFF, 0xFE, // 10 | 16-bit Destination Network Address
    0x41,       // 12 | Source endpoint - (0x41 arbitrarily selected)
    0x42,       // 13 | Destination enpoint - (0x42 arbitrarily selected)
    0x00, 0x00, // 14 | Cluster ID - 0x0000 is ZCL Basic Cluster
    0x00, 0xD1, // 16 | Profile ID - set to 0x00 for ZDO transmissions
                // (Profile ID 0x00D1 arbitrarily selected) 
    0x00,       // 18 | Broadcast Radius
    0x00,       // 19 | Transmit Options
    0x00,       // 20 | Data payload: ZCL Frame Header: Frame control
    0x01,       // 21 | ZCL Frame Header: Transaction Sequence Number
    0x00,       // 22 | ZCL Frame Header: Command ID - 0x00 is the Read Attributes command
    0x03, 0x00  // 23 | ZCL Payload: Attribute ID - 0x00003 (Hardware Version attribute) send little endian
                // (This can actually be a list of attributes to read)
  };
  frame[1] = nextFrameID();
  frame[21] = nextFrameID();
  
  insertLittleEndian(frame, addrL, 2, 4);
  insertLittleEndian(frame, addrH, 6, 4);
  String bf = new String(frame);
  return makeUARTdataFrame(string2array(bf),false);
}

void sendSomeATcommands(String start, int n) {
  // If n is to large, some of the responses will be lost.
  // n == 10 seems to work
  int index = findATcommand(start);
  if (index != -1) {
    int end = min((index + n) * 2, ATcommands.length);
    for (int i = index * 2; i < end; i += 2) {
      sendToUART(makeATcommmandFrame(ATcommands[i], "", false));
    }  
  }
}

void insertLittleEndian(char[] frame, int value, int start, int n) {
  for (int i = 0; i < n; i++) {
    frame[start++] = char(value & 0xFF);
    value >>= 8;
  }
}
    
void insertBigEndian(char[] frame, int value, int start, int n) {
  int idx;
  for (int i = 0; i < n; i++) {
    frame[n + --start] = char(value & 0xFF);
    value >>= 8;
  }
}
    
String makeATcommmandFrame(String command, String params, boolean escaped) {
  String frameData = str(char(0x08)) + str(nextFrameID()) + command + params;
  return makeUARTdataFrame(string2array(frameData),escaped);
}

char nextFrameID() {
  frameID++;
  if (frameID > 126) {
    frameID = 1;
  }
  return char(frameID);
}

/*
 * For a basic data frame:
 * * Prepend with framing character (0x7E) and 2 byte length
 * * Calculate and append the 1 byte checksum
 * * Insert escape characters if escaped == true
 */
String makeUARTdataFrame(char frameData[], boolean escaped) {
  int length = frameData.length;
  int lengthMSB = length >> 8;
  int lengthLSB = length & 0xFF;
  int checkSum = 0;
  String result = "";
  for (int i = 0; i < length; i++) {
    checkSum += frameData[i];
  }
  checkSum = 0xFF - (checkSum & 0xFF);
  if (escaped) {
    result = str(char(0x7E)) + XBeeAPIescape(char(lengthMSB)) + XBeeAPIescape(char(lengthLSB));
    for (int i = 0; i < length; i++) {
      result += XBeeAPIescape(frameData[i]);
    }
    result += XBeeAPIescape(char(checkSum));
  } 
  else {
    String frameDataString = new String(frameData);
    result = "" + char(0x7E) + char(lengthMSB) + char(lengthLSB) + frameDataString + char(checkSum);
  }
  return result;
}

void sendToUART(String pkt) {
  myPort.write(pkt);
  println();
  println();
  printTime();
  print(" uart>>>: ");
  printStringAsHex(pkt);
  // println(pkt);  // Some of the character printed make it so copy & paste doesn't work from the terminal screen to gedit
}

char[] string2array(String s) {
  char a[] = new char[s.length()];
  for (int i = 0; i < s.length(); i++) {
    a[i] = s.charAt(i);
  }
  return a;
}

void printStringAsHex(String s) {
  for (int i = 0; i < s.length(); i++) {
    print(hex(char(s.charAt(i)),2) + " ");
  }
  println();
}

void printTime() {
  print(secondCount+float(delayCount)/1000.0);
}

void populateHashMap(HashMap hm, String[] data) {
  for (int i = 0; i < data.length; i += 2) {
    hm.put(data[i], data[i+1]);
  }
}

void printHexFromPacket(String label, char[] pkt, int offset, int n) {
  print(label + ": ");
  for (int i = 0; i < n; i++) {
    print(hex(pkt[offset++],2) + " ");
  }
}

void printTextFromPacket(String label, char[] pkt, int offset, int n) {
  print(label + ": ");
  for (int i = 0; i < n; i++) {
    print(pkt[offset++]);
  }
}

String textStringFromPacket(char[] pkt, int offset, int n) {
  String result = new String(pkt);
  result = result.substring(offset,offset+n);
  return result;
}

String hexStringFromPacket(String label, char[] pkt, int offset, int n, String separator) {
  String result = label;
  for (int i = 0; i < n; i++) {
    result += hex(pkt[offset++],2) + separator;
  }
  return result;
}

void printHashMap(HashMap hm) {
  println();
  if (hm.isEmpty()) {
    println("empty");
  }
  Iterator i = hm.entrySet().iterator();  // Get an iterator
  while (i.hasNext()) {
    Map.Entry me = (Map.Entry)i.next();
    print(me.getKey() + ": ");
    println(me.getValue());
  }
}

String hashMapRandomValue(HashMap hm) {
  //  String[] x = (String[]) v.toArray(new String[0]);
  Collection hmc = hm.values();
  String[] hmvalues = (String[]) hmc.toArray(new String[0]);
  String result = hmvalues[int(random(hm.size()))];
  // return (String[]) hm.values().toArray(new String[0])[int(random(hm.size()))];
  return result;
}

int findATcommand(String atCommand) {
  int result = -1;
  for (int i = 0; i < ATcommands.length; i +=2) {
    if (atCommand.equals(ATcommands[i])) {
      result = i/2;
      break;
    }
  }
  return result;
}

void moveATcursor(int step) {
  int len = ATcommands.length/2;
  ATcursor += step;
  if (ATcursor < 0) {
    ATcursor = len - 10;
  } else if (ATcursor >= len) {
    ATcursor = 0;
  }
}

void getDisplayData() {
  sendToUART(makeATcommmandFrame("SH", "", false));  // Serial Number High
  sendToUART(makeATcommmandFrame("SL", "", false));  // Serial Number Low
  sendToUART(makeATcommmandFrame("MY", "", false));  // 16-bit Network Address
  sendToUART(makeATcommmandFrame("VR", "", false));  // Software version
  sendToUART(makeATcommmandFrame("CH", "", false));  // Operating Channel
}

/*
 * When the user presses certain keys we send a packet to the XBee
 */
void keyPressed() {
  // Send the keystroke out:
  switch (key) {
  case 'a':
    sendToUART(makeATcommmandFrame("SH", "", false));  // Serial Number High
    sendToUART(makeATcommmandFrame("SL", "", false));  // Serial Number Low
    sendToUART(makeATcommmandFrame("MY", "", false));  // 16-bit Network Address
    sendToUART(makeATcommmandFrame("MP", "", false));  // 16-bit Parent Network Address
    sendToUART(makeATcommmandFrame("CH", "", false));  // Operating Channel
    sendToUART(makeATcommmandFrame("ID", "", false));  // Extended PAN ID
    sendToUART(makeATcommmandFrame("OP", "", false));  // Operating Extended PAN ID
    break;  
  case 'A':
    sendToUART(makeATcommmandFrame("AC", "", false));  // Apply Changes
    break;  
  case 'c':
    // sendToUART(makeExplicitToCoordinatorFrame("1", false)); 
    sendToUART(makeExplicitToCoordinatorFrame("PHDreading.response:" + str(int(random(10))), false)); 
    break; 
  case 'd':
    displayFrame = !displayFrame;
    break;
  case 'D':
    displayIncomingBytes = !displayIncomingBytes;
    break;
  case 'h':
    printHashMap(addr64to16);
    printHashMap(addr16to64);
    break;
  case 'i':
    getDisplayData();
    break;
  case 'I':
    sendSomeATcommands("ID", 10);
    break;
  case 'j':
    moveATcursor(1);
    break;  
  case 'J':
    moveATcursor(10);
    break;
  case 'k':
    moveATcursor(-1);
    break;
  case 'K':
    moveATcursor(-10);
    break; 
  case 'l':
    sendSomeATcommands(ATcommands[ATcursor*2],1);
    break;
  case 'L':
    sendSomeATcommands(ATcommands[ATcursor*2],10);
    break;
  case 'm':
    sendToUART(makeBroadcastFrame("Tea42", false));
    break;  
  case 'n':
    sendToUART(makeATcommmandFrame("NJ", str(char(0xFF)), false));
    break;
  case 'o':
    sendToUART(makeATcommmandFrame("AO", str(char(0x01)), false));
    break;
  case 'O':
    sendToUART(makeATcommmandFrame("AO", str(char(0x03)), false));
    break;
  case 'p':
    sendToUART(makeATcommmandFrame("CB", str(char(0x02)), false));  // Causes the coordinator to permit joining
    break;
  case 'P':
    sendToUART(makeATcommmandFrame("CB", str(char(0x01)), false));  // Causes the router to attempt joining
    break;
  case 'r':
   running = !running;
   break;   
  case 's':
    sendSomeATcommands("SM", 3);
    sendSomeATcommands("SP", 2);
    break;
  case 'S':
    // sendToUART(makeATcommmandFrame(char(0x01), "SM", str(char(0x00))+str(char(0x00)), false));
    sendToUART(makeATcommmandFrame("SM", str(char(0x00)), false));
    break;
  case 't':
    sendToUART(makeATcommmandFrame("SM", "", false));
    break;  
  case 'T':
    sendToUART(makeATcommmandFrame("SM", str(char(0x00))+str(char(0x00)), false));
    break;  
  case 'v':
    sendSomeATcommands("VR", 4);
    break;
  case 'x':
    String payload = "You42 " + str(int(random(1000)));
    // println("PHDtest.request: " + payload);
    sendToUART(makeExplicitBroadcastFrame(payload , false));
    break;  
  case 'X':
    String addr64 = hashMapRandomValue(addr16to64);
    String addr16 = (String) addr64to16.get(addr64);
    String addr16a = str(char(0xFF))+str(char(0xFE));
    String payloadX = "Broach the coach " + str(int(random(1000)));
    println("Sending X: " + payloadX);
    sendToUART(makeExplicitAddrFrame(addr64, addr16, payloadX, false));
    break;
  case 'z':
    String addr64z = hashMapRandomValue(addr16to64);
    String addr16z = (String) addr64to16.get(addr64z);
    println("addr16z: " + addr16z);
    sendToUART(makeZDOactiveEndpointsRequestFrame(unhex(addr16z)));
    break;
    
  case '!':
    sendToUART(makeATcommmandFrame("RE", "", false));  // "Restore Defaults. Restore module parameters to factory defaults."
    break;
  case '#':
    sendToUART(makeATcommmandFrame("NR", "", false));  // "Network Reset. Force a node to disassociate from the network."
    break;
  default:
    myPort.write(key);
    break;
  }
  whichKey = key;
}


